import updateNodeElement from "../Dom/updateDomElement";
import { createTaskQueue, arrified, createStateNode, getTag, getRoot } from "../Misc";

const taskQueue = createTaskQueue();
let subTask = null;
let pendingCommit = null;

// fiber 是最外层的节点对象
const commitAllWork = fiber => {
  fiber.effects.forEach(item => {
    if (item.tag === 'class_component') {
      // 如果处理的是一个组件 把组件的 fiber 对象添加到组件的实例对象上
      item.stateNode.__fiber = item;
    }

    if (item.effectTag === 'placement') { // 创建新节点
      let currenFiber = item;
      let parentFiber = item.parent;
      // 如果当前 fiber 的父级是一个组件，组件是不可以直接 appendChild 的
      // 所以需要找父级的父级 直到找到一个是普通节点的父级 再将当前节点插入
      while (parentFiber.tag === 'class_component' || parentFiber.tag === 'function_component') {
        parentFiber = parentFiber.parent;
      }
      if (currenFiber.tag === 'host_component') {
        parentFiber.stateNode.appendChild(currenFiber.stateNode);
      }
    }
    else if (item.effectTag === 'update') { // 更新
      if (item.type === item.alternate.type) {
        // 新旧节点类型相同
        updateNodeElement(item.stateNode, item, item.alternate);
      } else {
        // 节点类型不同
        item.parent.stateNode.replaceChild(item.stateNode, item.alternate.stateNode);
      }
    }
    else if (item.effectTag === 'delete') { // 删除
      item.parent.stateNode.removeChild(item.stateNode);
    }
  });
  // 执行完后 备份旧的 fiber 对象
  fiber.stateNode.__rootFiberContainer = fiber;
}

// 1、从任务队列中获取任务 2、返回最外层节点的 fiber 对象
const getFirstTask = () => {
  const task = taskQueue.shift();
  if (task.from === 'class_component') {
    // 组件状态更新任务 重新构建根节点 fiber 对象
    const root = getRoot(task.instance);
    task.instance.__fiber.newState = task.newState;
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: 'host_root',
      effects: [],
      child: null,
      alternate: root // 看看是否有旧的 fiber 备份
    };;
  }

  return {
    props: task.props,
    stateNode: task.dom,
    tag: 'host_root',
    effects: [],
    child: null,
    alternate: task.dom.__rootFiberContainer // 看看是否有旧的 fiber 备份
  };
}

// 构建子 fiber 合集。参数中的 fiber 是最外层的 fiber 对象
const reconcileChildren = (fiber, children) => {
  // 1、children 有可能是一个对象也有可能是一个数组 统一改成数组
  const arrifiedChildren = arrified(children);
  let index = 0; // 当前构建到第几个子节点
  let numberOfElements = arrifiedChildren.length; // 子节点的长度
  let newFiber = null; // 当前子节点的 fiber 对象
  let prevFiber = null; // 上一个子节点的 fiber 对象
  let alternate = null;

  if (fiber.alternate && fiber.alternate.child) {
    // 如果有 alternate 属性，说明当前 fiber 对象有备份 fiber 对象
    // fiber.alternate.child 是 children 数据的第一项的备份
    alternate = fiber.alternate.child;
  }

  let element = null;
  while (index < numberOfElements || alternate) {
    element = arrifiedChildren[index]; // 当前子节点的虚拟dom

    if (element && alternate) { // 更新
      newFiber = { // 当前子节点虚拟dom对应的 fiber 对象
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'update',
        parent: fiber,
        alternate
      }
      if (element.type === alternate.type) {
        // 新旧节点类型相同
        newFiber.stateNode = alternate.stateNode;
      } else {
        // 类型不同
        newFiber.stateNode = createStateNode(newFiber);
      }
    }
    else if (element && !alternate) { // element存在并且备份不存在 说明是初始渲染
      newFiber = { // 当前子节点虚拟dom对应的 fiber 对象
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'placement',
        parent: fiber
      }
      // stateNode 存的是 fiber 对应的真实 dom 对象
      // 由于有多种情况 要分情况处理
      newFiber.stateNode = createStateNode(newFiber);
    }
    else if (!element && alternate) { // 删除
      alternate.effectTag = 'delete';
      fiber.effects.push(alternate);
    }

    // 如果是第一个子节点 那么把他对应的 fiber 对象作为父级的 child
    if (index === 0) fiber.child = newFiber;
    // 从第二个子节点开始 作为上一个子节点的 下一个兄弟节点
    else if (element) prevFiber.sibling = newFiber;

    // 在 while 循环过程中需要更新 alternate。找到循环项对应的备份 fiber 对象
    if (alternate && alternate.sibling) {
      alternate = alternate.sibling;
    } else {
      alternate = null;
    }

    prevFiber = newFiber;
    index++;
  }
}

const executeTask = fiber => {
  // 如果 fiber 是一个类组件 那么需要调用类实例的render获取children
  // 如果只是一个普通的节点 那么直接传入 props 中的children
  if (fiber.tag === 'class_component') {
    if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.newState) {
      // 更新组件状态
      fiber.stateNode.state = {
        ...fiber.stateNode.state,
        ...fiber.stateNode.__fiber.newState
      }
    }
    reconcileChildren(fiber, fiber.stateNode.render());
  }
  else if (fiber.tag === 'function_component') {
    reconcileChildren(fiber, fiber.stateNode(fiber.props));
  }
  else {
    reconcileChildren(fiber, fiber.props.children);
  }
  // 1、执行完一个任务的 fiber 先看这个 fiber 有没有子集 如果有，需要继续处理子集 ，找子集的子集
  if (fiber.child) {
    return fiber.child;
  }

  // 2、如果子集找到底了，那么需要找子集的兄弟节点
  let currentExecutelyFiber = fiber;
  // 判断 fiber 有没有父级 如果没有说明已经到 root 了 处理完了
  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
      currentExecutelyFiber.effects.concat([currentExecutelyFiber])
    )
    if (currentExecutelyFiber.sibling) { // 找子集的所有兄弟节点
      return currentExecutelyFiber.sibling;
    }
    // 兄弟节点都找完了，退回到父级 找父级的所有兄弟节点
    currentExecutelyFiber = currentExecutelyFiber.parent;
  }
  // 当复制 pendingCommit 说明已经找到了所有的 fiber 此时所有的 fiber 对象都存到了根节点的 effects 中
  // 此时的 currentExecutelyFiber 就是根节点
  pendingCommit = currentExecutelyFiber;
}

const workLoop = deadline => {
  // 1、判断要执行的任务存不存在
  if (!subTask) {
    subTask = getFirstTask();
  }
  // 2、如果任务存在且浏览器存在空余时间
  while (subTask && deadline.timeRemaining() > 1) {
    // 3、执行完一个任务 返回新的任务
    subTask = executeTask(subTask);
  }
  if (pendingCommit) {
    commitAllWork(pendingCommit);
  }
}

const performTask = deadline => {
  workLoop(deadline);
  // 走到这里 说明有优先级更高的任务打断了任务队列的执行 需要重新注册进去
  // 此时需要判断当前任务是否存在或者任务队列是否还有任务没有执行 如果有需要重新注册
  if (subTask || !taskQueue.isEmpty()) {
    requestIdleCallback(performTask);
  }
}

export const render = (element, dom) => {
  // 1、向任务队列中添加任务
  taskQueue.push({
    dom,
    props: {
      children: element
    }
  });
  // 2、指定在浏览器空闲时执行任务
  requestIdleCallback(performTask);
  /**
   * 任务就是通过 vdom 对象构建 fiber 对象
   * 任务队列就是一个数组，数组中存储各种任务
   */
}

/**
 * 
 * @param {*} instance 组件的实例对象
 * @param {*} newState setState传入的新值
 */
export const scheduleUpdate = (instance, newState) => {
  taskQueue.push({
    from: 'class_component',
    instance,
    newState
  });
  requestIdleCallback(performTask);
}